package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.constants.MQConstants;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResultVO;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.pojo.Goods;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author xiuer
 * @description 查询goods相关数据，封装到goods对象中。
 * @create 2019/7/1 9:59
 */
@Service
public class SearchService {

    @Autowired
    private ItemClient itemClient;
    @Autowired
    private GoodsRepository goodsRepository;
    @Autowired
    private ElasticsearchTemplate esTemplate;

    private QueryBuilder buildBasicQuery(SearchRequest searchRequest) {
        //健壮性判断
        String key = searchRequest.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        //1.构建Bool查询
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        //2.添加关键字查询条件
        MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("all", key).operator(Operator.AND);
        //3.将关键字查询添加到布尔查询
        queryBuilder.must(matchQueryBuilder);
        //4.添加过滤条件
        //4.1取出filter参数
        Map<String, String> filterMap = searchRequest.getFilter();

        if (!CollectionUtils.isEmpty(filterMap)) {
            for (String filterTerm : filterMap.keySet()) {
                //处理值（名称被改变所以先处理值）
                String value = filterMap.get(filterTerm);
                //处理名称
                if ("品牌".equals(filterTerm)) {
                    filterTerm = "brandId";
                } else if ("分类".equals(filterTerm)) {
                    filterTerm = "categoryId";
                } else {
                    filterTerm = "specs." + filterTerm;
                }
                //4.2 词条匹配查询term 添加过滤项名称，和过滤项值 到过滤条件条件中
                queryBuilder.filter(QueryBuilders.termQuery(filterTerm, value));
            }
        }
        return queryBuilder;
    }

    /**
     * 根据搜索栏关键字搜索分页查询-----页面下具体数据展示
     *
     * @param searchRequest
     * @return
     */
    public PageResultVO<GoodsDTO> searchGoods(SearchRequest searchRequest) {

        //构建原生查询器
        NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();
        searchQueryBuilder.withQuery(buildBasicQuery(searchRequest));
        //设置过滤条件
        searchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null));
        //设置分页条件
        searchQueryBuilder.withPageable(PageRequest.of(searchRequest.getPage() - 1, searchRequest.getSize()));
        //执行查询结果
        AggregatedPage<Goods> goodsPage = esTemplate.queryForPage(searchQueryBuilder.build(), Goods.class);
        //解析结果
        List<GoodsDTO> goodsDTOList = BeanHelper.copyWithCollection(goodsPage.getContent(), GoodsDTO.class);
        //封装并返回
        return new PageResultVO<>(goodsPage.getTotalElements(), goodsDTOList, goodsPage.getTotalPages());
    }

    /**
     * 过滤选项展示
     * 根据分类和品牌  根据规格参数（通过分类来确定规格参数）
     *
     * @param request
     * @return
     */
    public Map<String, List<?>> searchFilterGoods(SearchRequest request) {
        Map<String, List<?>> fiterMap = new HashMap<>();
        //准备搜索过滤条件
        NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();
        //设置搜索条件
        searchQueryBuilder.withQuery(buildBasicQuery(request));
        //设置分页条件（减少查询结果，只保留聚合结果）
        searchQueryBuilder.withPageable(PageRequest.of(0, 1));
        //设置过滤条件 显示空的过滤条件
        searchQueryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());
        //添加分类和品牌的聚合条件
        searchQueryBuilder.addAggregation(AggregationBuilders.terms("categoryAgg").field("categoryId"));
        searchQueryBuilder.addAggregation(AggregationBuilders.terms("brandAgg").field("brandId"));
        //获取聚合结果，得到品牌id集合，分类id集合
        AggregatedPage<Goods> goodsAggregatedPage = esTemplate.queryForPage(searchQueryBuilder.build(), Goods.class);
        //解析数据
        Aggregations aggregations = goodsAggregatedPage.getAggregations();
        //获取分类ids数据
        Terms categoryTerms = aggregations.get("categoryAgg");
        List<Long> cidList = getCidListByCategoryTerms(fiterMap, categoryTerms);
        //获取品牌bids结果
        Terms brandTrems = aggregations.get("brandAgg");
        getBidListByBrandTerms(fiterMap, brandTrems);

        //对分类进行聚合 [上面已完成，目的拿是为了到唯一分类，然后就可以确定规格参数]
        //判断分类是不是只剩下一个
        if (cidList != null && cidList.size() == 1) {
            //封装方法，对规格参数进行聚合
            handlerSpecParamAgg(fiterMap, cidList.get(0), request);
        }

        //存入结果并返回
        return fiterMap;

    }

    private void handlerSpecParamAgg(Map<String, List<?>> fiterMap, Long cid, SearchRequest request) {
        //根据分类id查询searching为true的规格
        List<SpecParamDTO> specParams = itemClient.querySepcParams(null, cid, true);
        // 准备搜索条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //设置搜索条件
        queryBuilder.withQuery(buildBasicQuery(request));
        //添加分页条件 （减少查询的分页结果只要聚合结果）
        queryBuilder.withPageable(PageRequest.of(0, 1));
        //添加过滤条件 （显示为空的过滤条件）
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());
        //添加聚合条件  （聚合的字段不确定 遍历然后对每一个规格参数添加聚合）

        for (SpecParamDTO param : specParams) {
            String name = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name));
        }
        //执行搜索结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        //解析聚合结果，放入map集合并返回
        Aggregations aggregations = result.getAggregations();
        for (SpecParamDTO param : specParams) {
            String name = param.getName();
            //根据聚合设定的名称name，来拿到Terms
            Terms paramTerms = aggregations.get(name);
            //获取桶
            List<? extends Terms.Bucket> buckets = paramTerms.getBuckets();
            List<String> paramListValue = buckets.stream().map(b -> b.getKeyAsString())
                    .filter(str -> StringUtils.isNotBlank(str)) //过滤 保留true （保留不为空的）
                    .collect(Collectors.toList());
            fiterMap.put(name, paramListValue);
        }
    }

    /*获取分类ids数据*/
    private List<Long> getCidListByCategoryTerms(Map<String, List<?>> fiterMap, Terms categoryTerms) {
        List<? extends Terms.Bucket> buckets = categoryTerms.getBuckets();
        List<Long> cidList = buckets.stream().map(b -> b.getKeyAsNumber())
                .map(number -> (Long) number).collect(Collectors.toList());
        //根据分类id集合查询分类 并存入map集合
        List<CategoryDTO> categoryDTOS = itemClient.queryCategoryByIds(cidList);
        fiterMap.put("分类", categoryDTOS);
        return cidList;
    }

    /*获取品牌bids结果*/
    private void getBidListByBrandTerms(Map<String, List<?>> fiterMap, Terms brandTrems) {
        List<? extends Terms.Bucket> buckets = brandTrems.getBuckets();
        List<Long> bidList = buckets.stream().map(b -> b.getKeyAsNumber())
                .map(number -> (Long) number).collect(Collectors.toList());
        //根据分类id集合查询分类 并存入map集合
        List<BrandDTO> brandDTOS = itemClient.queryBrandByBids(bidList);
        //根据品牌id集合查询品牌
        fiterMap.put("品牌", brandDTOS);
    }


//===============================================================================================
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
//***********************************************************************************************
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

    /**
     * 将一个spu对象转换为Goods对象
     *
     * @param spuDTO
     * @return
     */
    public Goods buildGoods(SpuDTO spuDTO) {
        Goods goods = new Goods();
        Long spuId = spuDTO.getId();
        //【1】.赋值 spuId
        goods.setId(spuId);
        //【2】.赋值 卖点(副标题)
        goods.setSubTitle(spuDTO.getSubTitle());
        //【3】.赋值 sku 的JSON结构（转成json目的不让elastic将sku对象进行自动拆分） //这里健壮性判断skus有没有被封装
        List<SkuDTO> list = CollectionUtils.isEmpty(spuDTO.getSkus()) ? itemClient.selectSku(spuId) : spuDTO.getSkus();
        List<Map<String, Object>> skuList = new ArrayList<>();
        for (SkuDTO skuDTO : list) {
            //准备map，取sku中部分数据,在封装到集合中去
            Map<String, Object> skuMap = new HashMap<>();
            skuMap.put("id", skuDTO.getId());
            skuMap.put("title", skuDTO.getTitle());
            skuMap.put("image", StringUtils.substringBefore(skuDTO.getImages(), ","));
            skuMap.put("price", skuDTO.getPrice());
            skuList.add(skuMap);
        }
        goods.setSkus(JsonUtils.toString(skuList));
        //【4】.赋值 所有需要被搜索的信息 all，包含标题（spu的name），分类（category名称），甚至品牌（品牌名称）
        //--分类名称
        String categoryNames = itemClient.queryCategoryByIds(spuDTO.getCategoryIds())
                .stream().map(CategoryDTO::getName).collect(Collectors.joining(","));
        //--品牌名称
        String brandName = itemClient.queryBrandByBid(spuDTO.getBrandId()).getName();
        goods.setAll(spuDTO.getName() + categoryNames + brandName);

        //【5】.赋值 品牌id
        goods.setBrandId(spuDTO.getBrandId());
        //【6】.赋值 商品三级分类id
        goods.setCategoryId(spuDTO.getCid3());
        //【7】.赋值 spu 创建时间
        goods.setCreateTime(spuDTO.getCreateTime().getTime());
        //【8】.赋值 spu 价格
        goods.setPrice(list.stream().map(SkuDTO::getPrice).collect(Collectors.toSet()));

        //【9】. 赋值 可搜索的规格参数，key是规格参数tb_spec_param name，值是sku spu相关商品值
        //定义map集合存储 规格key value
        Map<String, Object> specsMap = new HashMap<>();
        //获取key（规格参数表name）
        List<SpecParamDTO> params = itemClient.querySepcParams(null, spuDTO.getCid3(), true);
        //获取value   通用规格参数来自spuDetail
        SpuDetailDTO spuDetailDTO = spuDTO.getSpuDetail() == null ? itemClient.selectSpuDetail(spuId) : spuDTO.getSpuDetail();
        //取出通用规格参数值
        Map<Long, Object> genericSpecMap = JsonUtils.toMap(spuDetailDTO.getGenericSpec(), Long.class, Object.class);
        //取出特有规格参数值
        Map<Long, List<String>> specailSpecMap = JsonUtils.nativeRead(spuDetailDTO.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
        });
        //封装数据
        for (SpecParamDTO param : params) {
            //规格key
            String key = param.getName();
            //规格value
            Object value = null;
            //获取规格参数id ，根据id确定key 在根据key找到value
            Long id = param.getId();
            if (param.getGeneric()) {
                //通用规格参数 去genericSpec 获取值
                value = genericSpecMap.get(id);
            } else {
                //特有规格参数 去specailSpec 获取值
                value = specailSpecMap.get(id);
            }
            //判断是不是数值类型  将其转成段
            if (param.getNumeric()) {
                //是数字，需要分段
                value = chooseSegment(value, param);
            }

            specsMap.put(key, value);
        }
        goods.setSpecs(specsMap);

        return goods;


    }

    /**
     * value为数值类型的转换   不再直接存值，而是存对应的区间
     *
     * @param value
     * @param p
     * @return
     */
    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    //接收MQ 商品上架
    public void createIndex(Long id) {
        //查询spu
        SpuDTO spuDTO = itemClient.querySpuById(id);
        //构建索引库
        Goods goods = buildGoods(spuDTO);
        goodsRepository.save(goods);
    }

    //接收MQ 商品下架
    public void deleteIndex(Long id) {
        //删除指定的索引库数据
        goodsRepository.deleteById(id);

    }
}
